/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package Parser;

import DOM.Estructura.Atributo;
import DTD.DTDValidador;
import DTD.ParserDTD;
import Excepciones.*;
import Parser.Manejador.*;
import Parser.Validador.*;
import Principal.Principal;
import Utilidades.AtributoEtiqueta;
import Utilidades.LectorXML;
import Utilidades.StrOp;
import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;

/**
 * Realiza el parseo del archivo
 * @author Administrador
 */
public class Parser {
    private ClasificadorEtiqueta clasificador; //Clasifica las cadenas para determinar a que tipo de etiqueta corresponde
    private PilaEqulibrio estructura; //Maneja la estructura jerárquica del XML
    private File xml; //Objeto que representa el archivo XML a parsear
    private LectorXML lector; //Va leyendo el archivo durante el parseo
    private IManejadorError manejadorError; //Maneja las excepciones que pueden ocasionarse durante el parseo
    private IManejadorContenido manejadorContenido; //Maneja el parseo del contenido del XML
    private DTDValidador dtdValidador;
    private Principal principal;

    /* ****************************************
     *
     * CONSTRUCTOR
     *
     * ****************************************/

    /**
     * Constructor que crea un clasificador y una pila para controlar que la estructura sea correcta.
     * Recibe como parametro la interfaz grafica principal para poder ir actualizando el arbol
     * @param pPrincipal Interfaz grafica principal
     */
    public Parser(Principal pPrincipal) {
        principal = pPrincipal;
        clasificador = new ClasificadorEtiqueta();
        estructura   = new PilaEqulibrio();
    }

     /* ****************************************
     *
     * METODOS DE SET Y GET
     *
     * ****************************************/

    /**
     * Setea el archivo a analizar
     * @param archivoXML archivo XML a parsear
     */
    public void setXml(File archivoXML) {
        xml = archivoXML;
    }

    /**
     * Setea el manejador de error
     * @param manejadorErr nuevo manejador de error
     */
    public void setManejadorError(IManejadorError manejadorErr) {
        manejadorError = manejadorErr;
    }

    public void setManejadorContenido(ManejadorDOM manejadorCont){
        manejadorContenido = manejadorCont;
    }

    /* ****************************************
     *
     * METODOS PARA PARSEAR
     *
     * ****************************************/


    /**
     * Realiza el analisis del archivo
     * @throws FileNotFoundException
     */
    public void parsear() throws FileNotFoundException {
        estructura.clear();
        inicioDocumento();
        try {
            String etiquetaLeida; //variable auxiliar
            lector = new LectorXML(xml); // crea una instancia del LectorXML para leer el archivo
            inicioDocumento();

            do {
                etiquetaLeida = lector.getNext(); //lee el xml
                if (etiquetaLeida != null) {
                    clasificarEtiqueta(etiquetaLeida); //empieza a identificar la estructura del xml
                }
            } while (etiquetaLeida != null);

            if (estructura.esValido()) { //verifica si es valido
                finDocumento();
            }
            else {
                throw new ParserException(ParserException.ERROR_FATAL, estructura.identificarCausaError());
            }

        } catch (ParserException ex) {
            clasificarException(ex);
            principal.LanzarError();
        } finally {
            if (lector != null) {
                lector.close();
            }
        }
    }

    /**
     * Recibe una cadena de caracteres y determina que tipo de etiqueta es.
     * @param etiquetaLeida Cadena a analizar
     * @throws ParserException Tipo de excepcion que puede ocasionar el metodo
     */
    private void clasificarEtiqueta(String etiquetaLeida) throws ParserException {
        int tipoTag = clasificador.clasificar(etiquetaLeida); //Toma el tipo de etiqueta en base a lo que leyó

        //Si existe un Manejador de Contenido, busca cual es el tipo de tag y la procesa para escribirla en el arbol
        switch (tipoTag){
            case ClasificadorEtiqueta.INVALIDO:
                throw new ParserException(ParserException.ERROR_FATAL, "Sintaxis Inválida: " + etiquetaLeida);
                //No toma el break porque al salir por la excepcion nunca lo tomaría
            case ClasificadorEtiqueta.TAG_INICIO:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.INICIO);
                etiquetaDeApertura(etiquetaLeida);
                break;
            case ClasificadorEtiqueta.TAG_FINAL:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.FIN);
                etiquetaDeCierre(etiquetaLeida);
                break;
            case ClasificadorEtiqueta.TAG_AUTOCONTENIDO:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.NIVEL);
                etiguetaAutocontenida(etiquetaLeida);
                break;
            case ClasificadorEtiqueta.CONTENIDO:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.NIVEL);
                analizarContenido(etiquetaLeida,Atributo.TEXTO);
                break;
            case ClasificadorEtiqueta.CONTENIDO_CDATA:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.NIVEL);
                analizarContenido(etiquetaLeida,Atributo.CDATA);
                break;
            case ClasificadorEtiqueta.XML_PROLOGO:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.NIVEL);
                analizarAtributosPrologo(etiquetaLeida);
                break;
            case ClasificadorEtiqueta.DTD_INFO:
                principal.agregarTag(etiquetaLeida, Principal.tipoTAG.NIVEL);
                try {
                ParserDTD parserDTD = new ParserDTD(lector);//le paso el LectorXML al DTD
                dtdValidador = parserDTD.getDtdValidador();//le pido el validador
                } catch (DTDException ex) {
                    this.clasificarException(new ParserException(ParserException.ERROR, "DTD ERROR: "+ex.getMessage()));
                }
                break;
        }
    }

    /* ****************************************
     *
     * METODOS DE CLASIFICACIÓN DE ETIQUETAS
     *
     * ****************************************/


    /**
     * Crea el documento XML para guardarlo en memoria y a validarlo contra el DTD
     */
    private void inicioDocumento() {
        try {
            if(manejadorContenido != null)
                manejadorContenido.inicioDocumento();
            if (this.dtdValidador != null) {
                this.dtdValidador.validarInicioDocumento();
            }
        } catch (DTDException ex) {
            this.clasificarException(new ParserException(ParserException.ERROR, ex.getMessage()));
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }
    }

    /**
     * Cierra la pila que contenia las etiquetas y valida el resultado contra el DTD
     */
    private void finDocumento() {
        try {
            if(manejadorContenido != null)
                manejadorContenido.finDocumento();
            if (this.dtdValidador != null) {
                this.dtdValidador.validarFinalDocumento();
            }
        } catch (DTDException ex) {
            this.clasificarException(new ParserException(ParserException.ERROR, ex.getMessage()));
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }
    }

    /**
     * Valida que los atributos del Prologo sean los correctos
     * @param etiquetaLeida Etiqueta que corresponde al prologo a analizar
     */
    private void analizarAtributosPrologo(String etiquetaLeida) {
        AtributoEtiqueta[] variables = obtenerCaracteristicasAtributo(etiquetaLeida, "<?xml", "?>"); //obtenemos los atributos
        boolean correcto=ValidadorPrologo.validarAtributos(variables); //validamos

        try {
            if(manejadorContenido != null)
                manejadorContenido.prologoXML(variables);
            if(correcto)
                throw new ParserException(1, ValidadorPrologo.error);
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }
    }

    /**
     * Devuelve todos los pares nombre-valor de cada uno de los atributos de la etiqueta leida
     * @param etiquetaLeida Cadena que contiene el String de la etiqueta leída
     * @param tInicio cadena que representa el inicio del tag de acuerdo al tipo
     * @param tFinal cadena que representa el fin del tag de acuerdo al tipo
     * @return Vector que contiene el par atributo-valor de todos los atributos del tag
     */
    private AtributoEtiqueta[] obtenerCaracteristicasAtributo(String tagLeido, String tInicio, String tFinal) {
        String[] array = StrOp.separar(tagLeido, " ", tInicio, tFinal, '"', '"'); //obtiene un array de String con las opciones del tag separadas entre ""
        ArrayList<AtributoEtiqueta> variables = new ArrayList<AtributoEtiqueta>();

        for (int i = 0; i < array.length; i++) {
            String aux = array[i];
            //Si hay un igual es un par nombre-valor del atributo
            if (aux.indexOf('=') != -1) {
                int idxEquals = aux.indexOf('=');
                String nombre = aux.substring(0, idxEquals); //obtengo nombre del atributo
                String valor = aux.substring(idxEquals + 1).replaceAll("\"", ""); //obtengo valor
                variables.add(new AtributoEtiqueta(nombre, valor));
            }
        }
        return variables.toArray(new AtributoEtiqueta[variables.size()]);
    }

    /**
     * Procesa una etiqueta de apertura realizando las tareas para verificar el equilibrio y
     * validar con DTD
     * @param etiquetaLeida Cadena de la etiqueta
     */
    private void etiquetaDeApertura(String etiquetaLeida) {
        String nombreEtiqueta = StrOp.getNombreTag(etiquetaLeida, "<", ">"); //obtengo nombre
        AtributoEtiqueta[] variables = obtenerCaracteristicasAtributo(etiquetaLeida, "<", ">"); //atributos para validar con el DTD
        estructura.agregar(nombreEtiqueta, ClasificadorEtiqueta.TAG_INICIO); //agrego nombre en la pila

        try {
            if(manejadorContenido != null)
                manejadorContenido.etiquetaApertura(nombreEtiqueta, variables);
            if (this.dtdValidador != null) {
                this.dtdValidador.validarInicioElemento(nombreEtiqueta, etiquetaLeida, variables);
            }
        } catch (DTDException ex) {
            this.clasificarException(new ParserException(ParserException.ERROR, ex.getMessage()));
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }

    }

    /**
     * Procesa la etiqueta de cierre, extrayento la etiqueta de apertura de la pila
     * y validando con DTD
     * @param etiquetaLeida Cadena de la etiqueta
     */
    private void etiquetaDeCierre(String etiquetaLeida) {
        String nombreTag = StrOp.getNombreTag(etiquetaLeida, "</", ">"); //obtengo nombre etiqueta
        estructura.agregar(nombreTag, ClasificadorEtiqueta.TAG_FINAL); //verifico con la pila

        try {
            if(manejadorContenido != null)
                manejadorContenido.etiquetaCierre(nombreTag, etiquetaLeida);
            if (this.dtdValidador != null) {
                this.dtdValidador.validarFinalElemento(nombreTag, etiquetaLeida);
            }
        } catch (DTDException ex) {
            this.clasificarException(new ParserException(ParserException.ERROR, ex.getMessage()));
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }
    }

    /**
     * Procesa la etiqueta autocontenida
     * @param etiquetaLeida Cadena de la etiqueta autocontenida
     */
    private void etiguetaAutocontenida(String etiquetaLeida) {
        String nombreEtiqueta = StrOp.getNombreTag(etiquetaLeida, "<", "/>");
        AtributoEtiqueta[] variables = obtenerCaracteristicasAtributo(etiquetaLeida, "<", "/>"); //obtengo atributos
        estructura.agregar(nombreEtiqueta, ClasificadorEtiqueta.TAG_AUTOCONTENIDO); //agrego simbolicamente en la pila

        try {
            if(manejadorContenido != null){
                manejadorContenido.etiquetaApertura(nombreEtiqueta, variables);
                manejadorContenido.etiquetaCierre(nombreEtiqueta, etiquetaLeida);
            }
            if (this.dtdValidador != null) {
                this.dtdValidador.validarInicioElemento(nombreEtiqueta, etiquetaLeida, variables);
                this.dtdValidador.validarFinalElemento(nombreEtiqueta, etiquetaLeida);
            }
        } catch (DTDException ex) {
            this.clasificarException(new ParserException(ParserException.ERROR, ex.getMessage()));
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }
    }

    /**
     * Procesa el contenido de una etiqueta o el contenido una etiqueta CDATA
     * @param etiquetaLeida Contenido a procesar
     */
    private void analizarContenido(String etiquetaLeida, int tipoContenido) {
        try {
            if(manejadorContenido != null){
                manejadorContenido.agregarContenido(etiquetaLeida, tipoContenido);
            }
            if (this.dtdValidador != null) {
                this.dtdValidador.validarContenidoAInsertar(etiquetaLeida);
            }
        } catch (DTDException ex) {
            this.clasificarException(new ParserException(ParserException.ERROR, ex.getMessage()));
        } catch (ParserException ex) {
            this.clasificarException(ex);
        }
    }

    /* ****************************************
     *
     * METODOS DE MANEJADOR DE ERRORES
     *
     * ****************************************/

    /**
     * Determina la severidad de la excepción recibida por parámeto
     * @param ex Excepción a evaluar
     */
    private void clasificarException(ParserException ex) {
        int tipoException = ex.getTipo();

        if (manejadorError != null) {
            if (tipoException == ParserException.ERROR) {
                this.error(ex);
            }
            else if (tipoException == ParserException.ERROR_FATAL) {
                this.errorFatal(ex);
            }
            else if (tipoException == ParserException.WARNING) {
                this.warning(ex);
            }
        }
    }

    /**
     * Maneja la excepción recibida por parámetro, clasificada como un error fatal
     * @param ex Excepción procesar
     */
    private void errorFatal(ParserException ex) {
        try {
            manejadorError.errorFatal(ex);
        } catch (ParserException ex1) {
            this.clasificarException(ex1);
        }
    }

    /**
     * Maneja la excepción recibida por parámetro, clasificada como un error no grave
     * @param ex Excepción procesar
     */
    private void error(ParserException ex) {
        try {
            manejadorError.error(ex);
        } catch (ParserException ex1) {
            this.clasificarException(ex1);
        }
    }

    /**
     * Maneja la excepción recibida por parámetro, clasificada como una advertencia
     * @param ex Excepción procesar
     */
    private void warning(ParserException ex) {
        try {
            manejadorError.warning(ex);
        } catch (ParserException ex1) {
            this.clasificarException(ex1);
        }
    }


}
